/**
 * Requires multiple `var` declaration.
 *
 * Types: `Boolean` or `String` or `Object`
 *
 * Values: `true` or `"onevar"` or `allExcept: ['require']`
 *
 * If `requireMultipleVarDecl` defined as a `true` value, it will report only consecutive vars, if, on the other hand,
 * value equals to `"onevar"` string, `requireMultipleVarDecl` will allow only one `var` per function scope.
 *
 * If the value is `allExcept: ['require']`, then require statements are allowed to have a var declaration per variable.
 *
 * JSHint: [`onevar`](http://jshint.com/docs/options/#onevar)
 *
 * #### Example
 *
 * ```js
 * "requireMultipleVarDecl": true
 * ```
 *
 * ##### Valid
 *
 * ```js
 * var x = 1,
 *     y = 2;
 * ```
 *
 * ##### Valid for `allExcept: ['require']`
 *
 * ```js
 * var a = require("a");
 * var b = require("b");
 * var c = 1,
 *     d = 2;
 * ```
 *
 * ##### Invalid
 *
 * ```js
 * var x = 1;
 * var y = 2;
 * ```
 */

var assert = require('assert');

function consecutive(file, errors) {
    var isExceptRequire = this._isExceptRequire;

    file.iterateNodesByType('VariableDeclaration', function(node) {
        if (isExceptRequire && hasRequireStatements(node)) {
            return;
        }

        var sibling = node.nextSibling;

        if (!sibling) {
            return;
        }

        while (sibling.nextSibling) {
            sibling = sibling.nextSibling;

            if (sibling.type !== node.type) {
                break;
            }

            if (isExceptRequire && hasRequireStatements(sibling)) {
                break;
            }

            if (sibling.kind === node.kind) {
                errors.add(
                    node.kind[0].toUpperCase() + node.kind.slice(1) + ' declarations should be joined',
                    sibling
                );
            }
        }
    });
}

function _onevar(file, errors, node) {
    var firstVar = true;
    var firstConst = true;
    var firstParent = true;

    file.iterate(function(node) {
        var type = node.type;
        var kind = node.kind;

        // Don't go in nested scopes
        if (!firstParent && ['FunctionDeclaration', 'FunctionExpression'].indexOf(type) > -1) {
            return false;
        }

        if (firstParent) {
            firstParent = false;
        }

        if (type === 'VariableDeclaration') {
            if (kind === 'var') {
                if (!firstVar) {
                    errors.add('Var declarations should be joined', node);

                } else {
                    firstVar = false;
                }
            }

            if (kind === 'const') {
                if (!firstConst) {
                    errors.add('Const declarations should be joined', node);

                } else {
                    firstConst = false;
                }
            }
        }
    }, node);
}

function onevar(file, errors) {
    _onevar(file, errors, file.getProgram());
    file.iterateNodesByType(
        ['FunctionDeclaration', 'FunctionExpression'],
        _onevar.bind(this, file, errors)
    );
}

function hasRequireStatements(node) {
    if (!Array.isArray(node.declarations)) {
        return false;
    }

    return node.declarations.some(function(declaration) {
        var init = declaration.init;

        return init &&
            init.callee &&
            init.callee.name === 'require';
    });
}

module.exports = function() {};

module.exports.prototype = {
    configure: function(options) {
        var isExceptRequire = typeof options === 'object' &&
                              options.allExcept.length &&
                              options.allExcept.indexOf('require') !== -1;

        assert(
            options === true ||
            options === 'onevar' ||
            isExceptRequire,
            this.getOptionName() + ' option requires a true value, `onevar` or {allExcept: [\'require\']}'
        );

        var checkers = {
            'true': consecutive,
            onevar: onevar
        };

        this._isExceptRequire = isExceptRequire;
        this._check = isExceptRequire ? consecutive : checkers[options];
    },

    getOptionName: function() {
        return 'requireMultipleVarDecl';
    },

    check: function() {
        return this._check.apply(this, arguments);
    }
};
